/*
 * gamePlayer.c
 *
 * This is the client program, once a server has been set up the player run this program.
 *
 * The player must enter a port number as an argument
 *
 *  Created on: Dec 15, 2011
 *      Author: Jonathan Marokhovsky
 */
#include "gamePlayer.h"
#include "gameRules.h"
#include "msock.h"

#include <string.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <sys/ioctl.h>
#include <net/if.h>
#include <sys/wait.h>
#include <fcntl.h>
#include <unistd.h>

#define MAX_IN		100

int getIP(char addr[IP_LEN]);

int servSock; // this will be the server socket
int mySock; // this is this machine's socket for receiving info from the server
char avatar;

int main(int argc, char *argv[])
{
	int port, tempy, tempx, i, timeoutLen;
	int maxMapLen = TOTAL_HEIGHT * (MAP_WIDTH + 1);
	char currentMap[maxMapLen + 1];
	char timeoutMess[] = "Server Timeout";
	char name[MAX_IN];
	char input;
	char avStr[5];
	char *avPtr = avStr;
	char theIP[IP_LEN];
	char action[5]; // action messages will always only be 5 long
	WINDOW *win;
	int bytes; // this is the amount of bytes received from the server
	fd_set readSet;
	struct timeval servTimeout;
	struct timeval inputTimeout;
	struct sockaddr_in mySockAddr;

	/* Setup the timeouts */
	servTimeout.tv_sec = SERV_TIMEOUT;
	servTimeout.tv_usec = 0;
	inputTimeout.tv_sec = IDLE_TIME;
	inputTimeout.tv_usec = 0;

	/* Make sure that they supplied a port */
	if (argc != 2)
	{
		fprintf(stderr, "USAGE: %s <port #>\n", argv[1]);
		exit(1);
	}
	port = atoi(argv[1]);

	/* Before anything, set up the necessary ports */
	if ((servSock = msockcreate(SEND, SERV_IP, SERV_PORT)) < 0)
	{
		perror("ERROR: server socket creation");
		exit(EXIT_FAILURE);
	}

	/* Get the player's IP */
	if (getIP(theIP) != 0)
	{
		perror("ERROR: getting IP");
		exit(EXIT_FAILURE);
	}

	/* Make a UDP socket */
	if ((mySock = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP)) == -1)
	{
		perror("ERROR: player socket creation");
		exit(EXIT_FAILURE);
	}
	memset((char *) &mySockAddr, 0, sizeof(mySockAddr));
	mySockAddr.sin_family = AF_INET;
	mySockAddr.sin_port = htons(port);
	mySockAddr.sin_addr.s_addr = htonl(INADDR_ANY);
	if (bind(mySock, &mySockAddr, sizeof(mySockAddr)) == -1)
	{
		perror("ERROR: player socket bind");
		exit(EXIT_FAILURE);
	}

	/* Set the player's socket to nonblocking */
	if (fcntl(mySock, F_SETFL, O_NONBLOCK) < 0)
	{
		perror("ERROR: set nonblocking");
		exit(EXIT_FAILURE);
	}

	/* Prompt the user for a username and avatar */
	printf("Please enter the username you would like to use (10 characters): \n");
	scanf("%s", name);
	if (strlen(name) > MAX_NAME_LEN)
	{
		do
		{
			printf("Your name was too long, try again. Remember it must be less than 10 characters.\n");
			scanf("%s", name);
		} while (strlen(name) > MAX_NAME_LEN);
	}
	printf("Please enter a single character for your avatar (no special characters):\n");
	scanf("%s", avStr);
	if (strlen(avStr) > 1)
	{
		do
		{
			printf("Your avatar was too long, try again. Remember it must be 1 character.\n");
			scanf("%s", avStr);
		} while (strlen(avStr) > 1);
	}
	avatar = avStr[0];


	/* Now that the information has been taken in, try to connect to the server */
	printf("Now attempting to connect to the server...\n");
	connectToServer(name, avatar, port, servSock, theIP);

	/* Get the window ready */
	win = newWin();
	while (1)
	{
		/* Setup the readSet for the player's socket */
		FD_ZERO(&readSet);
		if (FD_SET(mySock, &readSet) < 0)
		{
			perror("ERROR: adding my socket to readset");
			cleanUp(EXIT_FAILURE);
		}
		bytes = select(mySock + 1, &readSet, NULL, NULL, &servTimeout);
		if (bytes <= 0)
		{
			timeoutLen = strlen(timeoutMess);
			tempy = MAP_HEIGHT / 2 - 1;
			tempx = MAP_WIDTH / 2 - (timeoutLen / 2);
			for (i = 0; i < timeoutLen; i++)
			{
				mvwaddch(win, tempy, tempx + i, timeoutMess[i]);
			}
			tempy += 2;
			tempx += 2;
			mvwaddch(win, tempy, tempx, 'E');
			mvwaddch(win, tempy, tempx + 1, 'x');
			mvwaddch(win, tempy, tempx + 2, 'i');
			mvwaddch(win, tempy, tempx + 3, 't');
			mvwaddch(win, tempy, tempx + 4, 'i');
			mvwaddch(win, tempy, tempx + 5, 'n');
			mvwaddch(win, tempy, tempx + 6, 'g');
			mvwaddch(win, tempy, tempx + 7, '.');
			mvwaddch(win, tempy, tempx + 8, '.');
			mvwaddch(win, tempy, tempx + 9, '.');
			wrefresh(win);
			sleep(1);
			cleanUp(1);
			break;
		}
		else
		{
			if (FD_ISSET(mySock, &readSet))
			{
				if (recv(mySock, currentMap, maxMapLen + 1, 0) < 0)
				{
					perror("ERROR: receiving the map");
					cleanUp(1);
				}
				else
				{
					displayWin(win, currentMap);
					servTimeout.tv_sec = SERV_TIMEOUT; // reset the timer
					if (FD_SET(0, &readSet) < 0)
					{
						perror("ERROR: adding my socket to readset");
						cleanUp(EXIT_FAILURE);
					}
					bytes = select(1, &readSet, NULL, NULL, &inputTimeout);
					if (bytes == 0)
					{
						// do nothing, this will start the loop all over again
					}
					else if (bytes < 0)
					{
						perror("ERROR: sending move");
						cleanUp(1);
					}
					else
					{
						if (FD_ISSET(0, &readSet))
						{
							/* See if the user input was an action */
							input = wgetch(win);
							inputTimeout.tv_sec = IDLE_TIME; // reset the input timer
							if (interpretAction(input))
							{
								sprintf(action, "%s%s%c%s%c", ACTION_FLAG, DELIM, avatar, DELIM, input);
								msend(servSock, action, 5);
							}
						}
						/* Otherwise it's not a key we care about start the loop over again. */
					}
				}
			}
		}
	}

	/* test out the screen */
	sleep(1);
	cleanUp(0);
	return 0;
}

int displayWin(WINDOW *win, char newScreen[])
{
	int i = 0, j = 0, k = 0;
	for (i = 0; i < TOTAL_HEIGHT; i++)
	{
		for (j = 0; j < MAP_WIDTH; j++)
		{
			mvwaddch(win, i, j, newScreen[k]);
			k++;
		}
		k++; // there's a newline after every "line", so skip this
	}
	wrefresh(win);
	return 0; // indicate that it closed correctly
}

WINDOW *newWin(void)
{
	WINDOW *ret;
	initscr();
	ret = newwin(0,0,0,0);
	leaveok(ret, TRUE);
	signal(SIGINT, cleanUp);
	nodelay(ret, TRUE);
	cbreak();
	noecho();
	curs_set(0);
	return ret;
}

void cleanUp(int signal)
{
	char *exitMess;
	endwin();
	sprintf(exitMess, "%s%s%c", EXIT_FLAG, DELIM, avatar);
	msend(servSock, exitMess, 3);
	msockdestroy(servSock);
	msockdestroy(mySock);
	exit(0);
}

int connectToServer(char *name, char avatar, int port, int serverSocket, char theIP[])
{
	int ret = 0;
	char portStr[6];
	char *avStr;
	char message[MAX_NAME_LEN + IP_LEN + 1 + 5 + 3]; // max name + ip length + 1 char (avatar) + 5 (max port length) + delim count
	strncpy(message, name, strlen(name));
	strncat(message, DELIM, 1);
	sprintf(avStr, "%c", avatar);
	strncat(message, avStr, 1);
	strncat(message, DELIM, 1);
	sprintf(portStr, "%d", port);
	strncat(message, theIP, strlen(theIP));
	strncat(message, DELIM, 1);
	strncat(message, portStr, strlen(portStr) + 1);
	if (msend(serverSocket, message, strlen(message) + 1) < 0)
	{
		perror("ERROR: sending connect message");
		cleanUp(1);
	}
	return ret;
}

/*
 * getIP() returns 0 if the IP was successfully given or -1 if there was an
 * error.  The IP will be stored in the addr argument.
 *
 * This assumes the machine is in IPv4 and has a wired connection on a linux box.
 *
 * This function is based on code from:
 * 		http://www.cnx-software.com/2011/04/05/c-code-to-get-mac-address-and-ip-address/
 */
int getIP(char addr[IP_LEN])
{
	int sock;
	int ret = 0;
	struct ifreq ifr;

	/* Make a dummy socket */
	if ((sock = socket(AF_INET, SOCK_DGRAM, 0)) < 0)
	{
		perror("ERROR: open socket");
		ret = EXIT_FAILURE;
	}
	else
	{
		/*
		 * Use ioctl() to determine the IP address of the socket which is on the
		 * machine, thus the machine's IP
		 */
		strcpy(ifr.ifr_name, "eth0"); // assume it's a wired connection on the ccc boxes
		if (ioctl(sock, SIOCGIFADDR, &ifr) < 0)
		{
			strcpy(addr, "0.0.0.0");
			perror("ERROR: reading ip");
			ret = EXIT_FAILURE;
		}
		sprintf(addr, "%s", inet_ntoa(((struct sockaddr_in *) &(ifr.ifr_addr))->sin_addr));
		close(sock);
	}
	return ret;
}

int interpretAction(char cmd)
{
	int ret = 1;
	switch (cmd)
	{
		case 'w':
		case 'W':
		case 'a':
		case 'A':
		case 's':
		case 'S':
		case 'd':
		case 'D':
		case 'i':
		case 'I':
		case 'j':
		case 'J':
		case 'k':
		case 'K':
		case 'l':
		case 'L':
			break;
		default:
			ret--;
			break;
	}
	return ret;
}

